package com.linkgie.galaxyframework.utils;

import java.util.Arrays;

public class Base58Encoding {

	private static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray();
	private static final int BASE_58 = ALPHABET.length;
	private static final int BASE_256 = 256;

	private static final int[] INDEXES = new int[128];
	static {
		for (int i = 0; i < INDEXES.length; i++) {
			INDEXES[i] = -1;
		}
		for (int i = 0; i < ALPHABET.length; i++) {
			INDEXES[ALPHABET[i]] = i;
		}
	}

	public static String encode(byte[] input) {
		return encode(input, 0, input.length);
	}

	/**
	 * 对指定的字节数据编码为 base58 字符；
	 * 
	 * @param data 输入的字节数据
	 * @param offset 要编码的输入数据的起始位置；
	 * @param length 要编码的输入数据的长度；如果长度超出了输入数组已有的长度，则超出的部分以 0 (byte)填充。
	 * @return
	 */
	public static String encode(byte[] data, int offset, int length) {
		if (data.length == 0) {
			return "";
		}

		// 为了不改变原输入，复制一个副本，以便稍后处理时需要进行修改.
		byte[] buff = Arrays.copyOfRange(data, offset, offset + length);

		// Count leading zeroes
		int zeroCount = 0;
		while (zeroCount < buff.length && buff[zeroCount] == 0) {
			++zeroCount;
		}

		// Encoding;
		byte[] temp = new byte[buff.length * 2];
		int j = temp.length;

		int startAt = zeroCount;
		while (startAt < buff.length) {
			byte mod = divmod58(buff, startAt);
			if (buff[startAt] == 0) {
				++startAt;
			}

			temp[--j] = (byte) ALPHABET[mod];
		}

		// Strip extra '1' if any
		while (j < temp.length && temp[j] == ALPHABET[0]) {
			++j;
		}

		// Add as many leading '1' as there were leading zeros.
		while (--zeroCount >= 0) {
			temp[--j] = (byte) ALPHABET[0];
		}

		byte[] output = Arrays.copyOfRange(temp, j, temp.length);
		return new String(output);
	}

	public static byte[] decode(String base58Str) {
		if (base58Str.length() == 0) {
			return new byte[0];
		}

		byte[] bytes58 = new byte[base58Str.length()];
		
		for (int i = 0; i < base58Str.length(); ++i) {
			char c = base58Str.charAt(i);

			int idx58 = -1;
			if (c >= 0 && c < 128) {
				idx58 = INDEXES[c];
			}
			if (idx58 < 0) {
				throw new RuntimeException("The specified argument is not a Base58 string ddd: " + base58Str);
			}

			bytes58[i] = (byte) idx58;
		}

		// Count leading zeroes
		int zeroCount = 0;
		while (zeroCount < bytes58.length && bytes58[zeroCount] == 0) {
			++zeroCount;
		}

		// Encoding
		byte[] temp = new byte[base58Str.length()];
		int j = temp.length;

		int from = zeroCount;
		while (from < bytes58.length) {
			byte mod = divmod256(bytes58, from);
			if (bytes58[from] == 0) {
				++from;
			}

			temp[--j] = mod;
		}

		// Do no add extra leading zeroes, move j to first non null byte.
		while (j < temp.length && temp[j] == 0) {
			++j;
		}

		return Arrays.copyOfRange(temp, j - zeroCount, temp.length);
	}

	private static byte divmod58(byte[] nums, int from) {
		int remainder = 0;
		for (int i = from; i < nums.length; i++) {
			int digit256 = (int) nums[i] & 0xFF;
			int temp = remainder * BASE_256 + digit256;

			nums[i] = (byte) (temp / BASE_58);

			remainder = temp % BASE_58;
		}

		return (byte) remainder;
	}

	private static byte divmod256(byte[] num58, int from) {
		int remainder = 0;
		for (int i = from; i < num58.length; i++) {
			int digit58 = (int) num58[i] & 0xFF;
			int temp = remainder * BASE_58 + digit58;

			num58[i] = (byte) (temp / BASE_256);

			remainder = temp % BASE_256;
		}

		return (byte) remainder;
	}
}